package com.chris.cc.gateway.service.impl;

import cn.hutool.json.JSONUtil;
import com.chris.cc.gateway.mapper.RouteDefinitionMapper;
import com.chris.cc.gateway.model.dto.RouteDefinitionDTO;
import com.chris.cc.gateway.model.entity.RouteDefinitionEntity;
import com.chris.cc.gateway.service.RouteService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Copyright (c) 2018-2025, chris All rights reserved.
 * <p>
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * <p>
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * <p>
 * Author: haoka (haokang207@126.com)
 * <p>
 * Date: 2018/10/10 15:16
 **/
@Service
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    @Autowired
    private RouteDefinitionLocator routeDefinitionLocator;
    @Autowired
    private RouteDefinitionMapper routeDefinitionMapper;
    private ApplicationEventPublisher publisher;

    @PostConstruct
    public void init() {
        //启动后加载
        List<RouteDefinitionEntity> entities = routeDefinitionMapper.selectAll();
        for (RouteDefinitionEntity entity : entities) {
            RouteDefinition definition = new RouteDefinition();
            definition.setId(entity.getId());
            definition.setUri(URI.create(entity.getUri()));
            definition.setOrder(entity.getOrder());
            String predicates = entity.getPredicates();
            if (StringUtils.isNotBlank(predicates)) {
                definition.setPredicates(JSONUtil.parseArray(predicates).toList(PredicateDefinition.class));
            }
            String filters = entity.getFilters();
            if (StringUtils.isNotBlank(filters)) {
                definition.setFilters(JSONUtil.parseArray(filters).toList(FilterDefinition.class));
            }
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @Override
    public void add(RouteDefinitionDTO route) {
        RouteDefinition definition = new RouteDefinition();
        definition.setUri(URI.create(route.getUri()));
        definition.setOrder(route.getOrder());
        if (StringUtils.isNotBlank(route.getId())) {
            definition.setId(route.getId());
        }
        String predicates = route.getPredicates();
        if (StringUtils.isNotBlank(predicates)) {
            definition.setPredicates(JSONUtil.parseArray(predicates).toList(PredicateDefinition.class));
        }
        String filters = route.getFilters();
        if (StringUtils.isNotBlank(filters)) {
            definition.setFilters(JSONUtil.parseArray(filters).toList(FilterDefinition.class));
        }
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        RouteDefinitionEntity entity = new RouteDefinitionEntity(definition.getId(), route.getUri(), route.getOrder(),
                predicates, filters, new Date(), null);
        routeDefinitionMapper.insert(entity);
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public Page<RouteDefinition> query(Pageable page) {
        List<RouteDefinition> list = new ArrayList<>();
        Flux<RouteDefinition> routeDefinitions = routeDefinitionLocator.getRouteDefinitions();
        routeDefinitions.collectList().single().subscribe(list::addAll).dispose();
        int start = Long.valueOf(page.getOffset()).intValue();
        int end = start + page.getPageSize();
        end = end > list.size() ? list.size() : end;
        if (start > end) {
            return null;
        }
        return new PageImpl<>(list.subList(start, end), page, list.size());
    }

    @Override
    public void delete(String id) {
        routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        routeDefinitionMapper.deleteByPrimaryKey(id);
    }

    @Override
    public void update(RouteDefinitionDTO route) {
        RouteDefinition routeDefinition = get(route.getId());
        if (routeDefinition != null) {
            delete(routeDefinition.getId());
        }
        add(route);
    }

    @Override
    public RouteDefinition get(String id) {
        if (StringUtils.isBlank(id)) {
            return null;
        }
        List<RouteDefinition> list = new ArrayList<>();
        Flux<RouteDefinition> routeDefinitions = routeDefinitionLocator.getRouteDefinitions();
        routeDefinitions.collectList().single().subscribe(list::addAll).dispose();
        for (RouteDefinition definition : list) {
            if (definition.getId().equals(id)) {
                return definition;
            }
        }
        return null;
    }
}
